import { AstTreeUtils } from "arkanalyzer";
import { ts, NodePredicate } from "qvog-dsl";
import { native, ObjectLiteralExpression, rule, HomeCheckHint } from "qvog-dsl";

const SEVERITY = 3;
const DOC_PATH = '';
const DESCRIPTION = 'May have side effects';

export const Statement: NodePredicate<ts.Statement> = {
    is(node: ts.Node): node is ts.Statement {
        return ts.isVariableStatement(node) || 
        ts.isExpressionStatement(node) || 
        ts.isForInStatement(node) || 
        ts.isForOfStatement(node) || 
        ts.isContinueStatement(node) || 
        ts.isIfStatement(node) || 
        ts.isDoStatement(node) || 
        ts.isWhileStatement(node) || 
        ts.isForStatement(node) || 
        ts.isBreakStatement(node) || 
        ts.isReturnStatement(node) || 
        ts.isWithStatement(node) || 
        ts.isSwitchStatement(node) || 
        ts.isLabeledStatement(node) || 
        ts.isThrowStatement(node) || 
        ts.isTryStatement(node);
    },
};

export default native<HomeCheckHint>()()
    .match(Statement)
    .when((node: ts.Statement): boolean => {
        if (node.parent === node.getSourceFile()) {
            const nodes = node.getChildren(node.getSourceFile());
            if (isWarningMember(node, nodes)) {
                return true;
            }
        }
        return false;
    })
    .report({
        severity: SEVERITY,
        description: DESCRIPTION,
        docPath: DOC_PATH,
    });

function isWarningMember(member: ts.Node, childNodes: ts.Node[]): boolean {
    const hasSpecificChildInBlock = (blockNode: ts.Node, checkNodeTypes: (node: ts.Node) => boolean): boolean => {
        const blockNodeChildren = blockNode.getChildren();
        const syntaxListNodes = blockNodeChildren.filter(child => child.kind === ts.SyntaxKind.SyntaxList);

        return syntaxListNodes.some(syntaxListNode => {
            const syntaxListChildren = syntaxListNode.getChildren();
            return syntaxListChildren.some(checkNodeTypes);
        });
    };

    switch (member.kind) {
        case ts.SyntaxKind.ExpressionStatement:
            return childNodes.some(isSpecificExpression);

        case ts.SyntaxKind.IfStatement: {
            const blockNodes = childNodes.filter(child => ts.isBlock(child));
            return blockNodes.some(blockNode =>
                hasSpecificChildInBlock(blockNode, child =>
                    ts.isVariableStatement(child) || ts.isExpressionStatement(child)
                )
            );
        }

        case ts.SyntaxKind.DoStatement:
        case ts.SyntaxKind.WhileStatement:
        case ts.SyntaxKind.ForStatement:
        case ts.SyntaxKind.ForInStatement:
        case ts.SyntaxKind.ForOfStatement:
        case ts.SyntaxKind.SwitchStatement:
        case ts.SyntaxKind.TryStatement:

            return true;

        case ts.SyntaxKind.Block: {
            const blockSyntaxListNodes = childNodes.filter(child => child.kind === ts.SyntaxKind.SyntaxList);
            return blockSyntaxListNodes.some(syntaxListNode =>
                syntaxListNode.getChildren().some(child => ts.isExpressionStatement(child))
            );
        }

        case ts.SyntaxKind.VariableStatement: {
            const variableDeclarationListNodes = childNodes.filter(child => ts.isVariableDeclarationList(child));
            return variableDeclarationListNodes.some(isVariableDeclarationWithCall);
        }

        default:
            return false;
    }
}

function isSpecificExpression(node: ts.Node): boolean {
    return (
        ts.isCallExpression(node) ||
        ts.isPropertyAccessExpression(node) ||
        ts.isBinaryExpression(node) ||
        ts.isNewExpression(node)
    );
};

function isVariableDeclarationWithCall(variableDeclarationListNode: ts.Node): boolean {
    const varNodeChildren = variableDeclarationListNode.getChildren();
    const syntaxListNodes = varNodeChildren.filter(child => child.kind === ts.SyntaxKind.SyntaxList);

    return syntaxListNodes.some(syntaxListNode => {
        const syntaxListChildren = syntaxListNode.getChildren();
        const varNodes = syntaxListChildren.filter(child => ts.isVariableDeclaration(child));

        return varNodes.some(varNode => {
            const varNodeChildrens = varNode.getChildren();
            return varNodeChildrens.some(child => ts.isCallExpression(child));
        });
    });
};
